package org.wordpress.android.ui.reader.views;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import androidx.annotation.NonNull;

import org.wordpress.android.WordPress;
import org.wordpress.android.fluxc.network.UserAgent;
import org.wordpress.android.fluxc.store.AccountStore;
import org.wordpress.android.fluxc.module.OkHttpClientQualifiers;
import org.wordpress.android.ui.WPWebView;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.UrlUtils;
import org.wordpress.android.util.WPUrlUtils;
import org.wordpress.android.util.helpers.WebChromeClientWithVideoPoster;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;
import javax.inject.Named;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/*
 * WebView descendant used by ReaderPostDetailFragment - handles
 * displaying fullscreen video and detecting url/image clicks
 */
public class ReaderWebView extends WPWebView {
    public interface ReaderWebViewUrlClickListener {
        @SuppressWarnings("SameReturnValue")
        boolean onUrlClick(String url);

        boolean onPageJumpClick(String pageJump);

        boolean onImageUrlClick(String imageUrl, View view, int x, int y);

        boolean onFileDownloadClick(String fileUrl);
    }

    public interface ReaderCustomViewListener {
        void onCustomViewShown();

        void onCustomViewHidden();

        ViewGroup onRequestCustomView();

        ViewGroup onRequestContentView();
    }

    public interface ReaderWebViewPageFinishedListener {
        void onPageFinished(WebView view, String url);
    }

    /**
     * Timeout in milliseconds for read / connect timeouts
     */
    private static final int TIMEOUT_MS = 30000;

    private ReaderWebChromeClient mReaderChromeClient;
    private ReaderCustomViewListener mCustomViewListener;
    private ReaderWebViewUrlClickListener mUrlClickListener;
    private ReaderWebViewPageFinishedListener mPageFinishedListener;

    private static String mToken;
    private static boolean mIsPrivatePost;
    private static boolean mBlogSchemeIsHttps;

    private boolean mIsDestroyed;

    @Inject UserAgent mUserAgent;
    @Inject AccountStore mAccountStore;
    @Inject @Named(OkHttpClientQualifiers.REGULAR) OkHttpClient mOkHttpClient;

    public ReaderWebView(Context context) {
        super(context);
        init(context);
    }

    public ReaderWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ReaderWebView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    @SuppressLint("NewApi")
    private void init(Context context) {
        ((WordPress) context.getApplicationContext()).component().inject(this);
        setBackgroundColor(Color.TRANSPARENT);
        if (!isInEditMode()) {
            mToken = mAccountStore.getAccessToken();

            mReaderChromeClient = new ReaderWebChromeClient(this);
            this.setWebChromeClient(mReaderChromeClient);
            this.setWebViewClient(new ReaderWebViewClient(this, mUserAgent, mOkHttpClient));
            this.getSettings().setUserAgentString(mUserAgent.getWebViewUserAgent());

            // Enable third-party cookies since they are disabled by default;
            // we need third-party cookies to support authenticated images
            CookieManager.getInstance().setAcceptThirdPartyCookies(this, true);
            this.setDownloadListener(
                    (url, userAgent, contentDisposition, mimetype, contentLength) -> {
                        if (hasUrlClickListener()) {
                            mUrlClickListener.onFileDownloadClick(url);
                        }
                    });
        }
    }

    @Override
    public void destroy() {
        mIsDestroyed = true;
        super.destroy();
    }

    public boolean isDestroyed() {
        return mIsDestroyed;
    }


    public void clearContent() {
        loadUrl("about:blank");
    }

    private ReaderWebViewUrlClickListener getUrlClickListener() {
        return mUrlClickListener;
    }

    public void setUrlClickListener(ReaderWebViewUrlClickListener listener) {
        mUrlClickListener = listener;
    }

    private boolean hasUrlClickListener() {
        return (mUrlClickListener != null);
    }

    private ReaderWebViewPageFinishedListener getPageFinishedListener() {
        return mPageFinishedListener;
    }

    public void setPageFinishedListener(ReaderWebViewPageFinishedListener listener) {
        mPageFinishedListener = listener;
    }

    private boolean hasPageFinishedListener() {
        return (mPageFinishedListener != null);
    }

    public void setCustomViewListener(ReaderCustomViewListener listener) {
        mCustomViewListener = listener;
    }

    private boolean hasCustomViewListener() {
        return (mCustomViewListener != null);
    }

    private ReaderCustomViewListener getCustomViewListener() {
        return mCustomViewListener;
    }

    public void setIsPrivatePost(boolean isPrivatePost) {
        mIsPrivatePost = isPrivatePost;
    }

    public void setBlogSchemeIsHttps(boolean blogSchemeIsHttps) {
        mBlogSchemeIsHttps = blogSchemeIsHttps;
    }

    private static boolean isValidClickedUrl(String url) {
        // only return true for http(s) urls so we avoid file: and data: clicks
        return (url != null && (url.startsWith("http") || url.startsWith("wordpress:")));
    }

    public boolean isCustomViewShowing() {
        return mReaderChromeClient.isCustomViewShowing();
    }

    public void hideCustomView() {
        if (isCustomViewShowing()) {
            mReaderChromeClient.onHideCustomView();
        }
    }

    /***
     * Checks if the tapped image is a child of an anchor tag we want to respect.
     * If so, we want to ignore the image click so that the wrapping src gets handled.
     * The additional URL grab is an additional check for the appropriate host or URL structure.
     *
     * Cases:
     * 1. Instagram
     * 2. The Story block
     *
     * @param hr - the HitTestResult
     * @return true if the image click should be ignored and deferred to the parent anchor tag
     */
    private boolean isValidEmbeddedImageClick(HitTestResult hr) {
        // Referenced https://pacheco.dev/posts/android/webview-image-anchor/
        if (hr.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
            Handler handler = new Handler();
            Message message = handler.obtainMessage();

            this.requestFocusNodeHref(message);
            String url = message.getData().getString("url");
            if (url == null) {
                return false;
            }

            return url.contains("ig_embed") || url.contains("wp-story");
        } else {
            return false;
        }
    }

    private boolean isVideoPressPreview(@NonNull String url) {
        return url.startsWith("https://videos.files.wordpress.com");
    }

    /*
     * detect when a link is tapped
     */
    @SuppressLint("ClickableViewAccessibility") // works as is
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP && mUrlClickListener != null) {
            HitTestResult hr = getHitTestResult();
            String url = hr.getExtra();
            if (isValidClickedUrl(url)) {
                if (UrlUtils.isImageUrl(url)) {
                    if (isValidEmbeddedImageClick(hr) || isVideoPressPreview(url)) {
                        return super.onTouchEvent(event);
                    } else {
                        return mUrlClickListener.onImageUrlClick(
                            url,
                            this,
                            (int) event.getX(),
                            (int) event.getY());
                    }
                } else {
                    return mUrlClickListener.onUrlClick(url);
                }
            } else {
                String pageJump = UrlUtils.getPageJumpOrNull(url);
                if (null != pageJump) {
                    return mUrlClickListener.onPageJumpClick(pageJump);
                }
            }
        }
        return super.onTouchEvent(event);
    }

    private static class ReaderWebViewClient extends WebViewClient {
        private final ReaderWebView mReaderWebView;
        private final UserAgent mUserAgent;
        private final OkHttpClient mOkHttpClient;

        ReaderWebViewClient(ReaderWebView readerWebView, UserAgent userAgent, OkHttpClient okHttpClient) {
            if (readerWebView == null) {
                throw new IllegalArgumentException("ReaderWebViewClient requires readerWebView");
            }
            mReaderWebView = readerWebView;
            mUserAgent = userAgent;
            mOkHttpClient = okHttpClient;
        }



        @Override
        public void onPageFinished(WebView view, String url) {
            if (mReaderWebView.hasPageFinishedListener()) {
                mReaderWebView.getPageFinishedListener().onPageFinished(view, url);
            }
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            // fire the url click listener, but only do this when webView has
            // loaded (is visible) - have seen some posts containing iframes
            // automatically try to open urls (without being clicked)
            // before the page has loaded
            return view.getVisibility() == View.VISIBLE
                   && mReaderWebView.hasUrlClickListener()
                   && isValidClickedUrl(url)
                   && mReaderWebView.getUrlClickListener().onUrlClick(url);
        }

        @SuppressWarnings("deprecation")
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            URL imageUrl = null;
            if (mIsPrivatePost && mBlogSchemeIsHttps && UrlUtils.isImageUrl(url)) {
                try {
                    imageUrl = new URL(UrlUtils.makeHttps(url));
                } catch (MalformedURLException e) {
                    AppLog.e(AppLog.T.READER, e);
                }
            }
            // Intercept requests for private images and add the WP.com authorization header
            if (imageUrl != null && WPUrlUtils.safeToAddWordPressComAuthToken(imageUrl)
                && !TextUtils.isEmpty(mToken)) {
                try {
                    Request request = new Request.Builder()
                            .url(imageUrl)
                            .addHeader("Authorization", "Bearer " + mToken)
                            .addHeader("User-Agent", mUserAgent.getWebViewUserAgent())
                            .addHeader("Connection", "Keep-Alive")
                            .build();

                    OkHttpClient client = mOkHttpClient.newBuilder()
                            .connectTimeout(TIMEOUT_MS, TimeUnit.MILLISECONDS)
                            .readTimeout(TIMEOUT_MS, TimeUnit.MILLISECONDS)
                            .build();

                    Response okResponse = client.newCall(request).execute();
                    if (okResponse.body() != null) {
                        return new WebResourceResponse(
                                okResponse.header("Content-Type"),
                                okResponse.header("Content-Encoding"),
                                okResponse.body().byteStream()
                        );
                    }
                } catch (IOException e) {
                    AppLog.e(AppLog.T.READER, e);
                }
            }

            return super.shouldInterceptRequest(view, url);
        }
    }

    private static class ReaderWebChromeClient extends WebChromeClientWithVideoPoster {
        private final ReaderWebView mReaderWebView;
        private View mCustomView;
        private CustomViewCallback mCustomViewCallback;

        ReaderWebChromeClient(ReaderWebView readerWebView) {
            super(readerWebView, org.wordpress.android.editor.R.drawable.media_movieclip);
            if (readerWebView == null) {
                throw new IllegalArgumentException("ReaderWebChromeClient requires readerWebView");
            }
            mReaderWebView = readerWebView;
        }

        /*
         * request the view that will host the fullscreen video
         */
        private ViewGroup getTargetView() {
            if (mReaderWebView.hasCustomViewListener()) {
                return mReaderWebView.getCustomViewListener().onRequestCustomView();
            } else {
                return null;
            }
        }

        /*
         * request the view that should be hidden when showing fullscreen video
         */
        private ViewGroup getContentView() {
            if (mReaderWebView.hasCustomViewListener()) {
                return mReaderWebView.getCustomViewListener().onRequestContentView();
            } else {
                return null;
            }
        }

        @Override
        public void onShowCustomView(View view, CustomViewCallback callback) {
            AppLog.i(AppLog.T.READER, "onShowCustomView");

            if (mCustomView != null) {
                AppLog.w(AppLog.T.READER, "customView already showing");
                onHideCustomView();
                return;
            }

            // hide the post detail content
            ViewGroup contentView = getContentView();
            if (contentView != null) {
                contentView.setVisibility(View.INVISIBLE);
            }

            // show the full screen view
            ViewGroup targetView = getTargetView();
            if (targetView != null) {
                targetView.addView(view);
                targetView.setVisibility(View.VISIBLE);
            }

            if (mReaderWebView.hasCustomViewListener()) {
                mReaderWebView.getCustomViewListener().onCustomViewShown();
            }

            mCustomView = view;
            mCustomViewCallback = callback;
        }

        @Override
        public void onHideCustomView() {
            AppLog.i(AppLog.T.READER, "onHideCustomView");

            if (mCustomView == null) {
                AppLog.w(AppLog.T.READER, "customView does not exist");
                return;
            }

            // hide the target view
            ViewGroup targetView = getTargetView();
            if (targetView != null) {
                targetView.removeView(mCustomView);
                targetView.setVisibility(View.GONE);
            }

            // redisplay the post detail content
            ViewGroup contentView = getContentView();
            if (contentView != null) {
                contentView.setVisibility(View.VISIBLE);
            }

            if (mCustomViewCallback != null) {
                mCustomViewCallback.onCustomViewHidden();
            }
            if (mReaderWebView.hasCustomViewListener()) {
                mReaderWebView.getCustomViewListener().onCustomViewHidden();
            }

            mCustomView = null;
            mCustomViewCallback = null;
        }

        boolean isCustomViewShowing() {
            return (mCustomView != null);
        }
    }
}
